package common;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

import math.LeastAbsMethod;
import math.LeastSquareMethod;
import math.WLeastAbsMethod;
import math.WLeastSquareMethod;
import math.common.MathCommon;
import math.probability.ExpDistribution;
import math.probability.NormalDistribution;
import math.probability.ProbDistribution;

public class EstimationProperties {

	ArrayList<Double> U;
	ArrayList<Double> V;
	double meanU;
	double meanV;

	private void modelData(ProbDistribution distX, ProbDistribution distKsi,
			ProbDistribution distEps, int N, Double b0, Double b1) {
		U = new ArrayList<Double>();
		V = new ArrayList<Double>();
		ArrayList<Double> smplX = distX.getSample(N);
		ArrayList<Double> ksi = distKsi.getSample(N);
		ArrayList<Double> eps = distEps.getSample(N);
		double sumU = 0.0;
		double sumV = 0.0;
		for (int i = 0; i < N; i++) {
			V.add(b0 + b1 * smplX.get(i) + eps.get(i));
			U.add(smplX.get(i) + ksi.get(i));
			sumU += U.get(i);
			sumV += V.get(i);
		}
		meanU = sumU / N;
		meanV = sumV / N;
		for (int i = 0; i < N; i++) {
			V.set(i, V.get(i) - meanV);
			U.set(i, U.get(i) - meanU);
		}

	}

	Double getStdDev(List<Double> x) {
		Double mX = 0.0;
		Double dX = 0.0;
		int n = x.size();
		for (int i = 0; i < n; i++) {
			mX += x.get(i);
		}
		mX = mX / n;
		for (int i = 0; i < n; i++) {
			dX += Math.pow(x.get(i) - mX, 2.0);
		}
		return Math.sqrt(dX / (n - 1));

	}

	Double getCov(List<Double> x, List<Double> y) {
		Double sum = 0.0;
		Double mX = 0.0;
		Double mY = 0.0;
		Double dX = 0.0;
		Double dY = 0.0;
		int n = x.size();
		for (int i = 0; i < n; i++) {
			mX += x.get(i);
			mY += y.get(i);
		}
		mX = mX / n;
		mY = mY / n;
		for (int i = 0; i < n; i++) {
			sum += (x.get(i) - mX) * (y.get(i) - mY);

		}
		return sum / n;
	}

	void testDistributionWithErrors(String fileName)
			throws FileNotFoundException {
		int N = 100;
		int M = 10000;
		Double b0 = 0.0;
		Double b1 = 10.0;
		Random rnd = new Random();
		ProbDistribution distX = new ExpDistribution(0.75);
		ProbDistribution distEps = new NormalDistribution(0.0, 0.1);
		ProbDistribution distKsi = new NormalDistribution(0.0, 1.0);
		PrintWriter out = new PrintWriter(new FileOutputStream(fileName, true));
		out.println("b_H1,bW_H1,b-bW_H1");
		for (int j = 0; j < M; j++) {
			modelData(distX, distKsi, distEps, N, b0, b1);
			Double b = new LeastSquareMethod().estimate(V, U);
			Double bW = new WLeastSquareMethod().estimate(V, U);
			Double dif = b - bW;
			out.println(b + "," + bW + "," + dif);
		}
		out.flush();
		out.close();
	}

	void testDistributionWithoutErrors(String fileName)
			throws FileNotFoundException {
		int N = 100;
		int M = 10000;
		Double b0 = 0.0;
		Double b1 = 10.0;
		Random rnd = new Random();
		ProbDistribution distX = new ExpDistribution(0.75);
		ProbDistribution distEps = new NormalDistribution(0.0, 0.1);
		ProbDistribution distKsi = new NormalDistribution(0.0, 0.0);
		PrintWriter out = new PrintWriter(new FileOutputStream(fileName, true));
		out.println("b_H0,bW_H0,b-bW_H0");
		for (int j = 0; j < M; j++) {
			modelData(distX, distKsi, distEps, N, b0, b1);
			Double b = new LeastSquareMethod().estimate(V, U);
			Double bW = new WLeastSquareMethod().estimate(V, U);
			Double dif = b - bW;
			out.println(b + "," + bW + "," + dif);
		}
		out.flush();
		out.close();
	}

	void testEstimatorVar() throws FileNotFoundException {
		int N = 100;
		int M = 10000;
		Double b0 = 0.0;
		Double b1 = 10.0;
		Random rnd = new Random();
		ArrayList<Double> bH1 = new ArrayList<Double>();
		ArrayList<Double> bWH1 = new ArrayList<Double>();
		ArrayList<Double> bH0 = new ArrayList<Double>();
		ArrayList<Double> bWH0 = new ArrayList<Double>();
		ArrayList<Double> difH0 = new ArrayList<Double>();
		ArrayList<Double> difH1 = new ArrayList<Double>();

		ProbDistribution distX = new ExpDistribution(0.75);
		ProbDistribution distEps = new NormalDistribution(0.0, 0.1);
		// ProbDistribution distKsi = new NormalDistribution(0.0, 1.0);
		// PrintWriter out = new PrintWriter(new FileOutputStream(fileName,
		// true));
		// out.println("b_H1,bW_H1,b-bW_H1");
		double S1 = 0;
		double S2 = 0;
		for (int j = 0; j < M; j++) {
			ProbDistribution distKsi = new NormalDistribution(0.0, 1.0);
			modelData(distX, distKsi, distEps, N, b0, b1);
			Double v1 = new LeastSquareMethod().estimate(V, U);
			Double v2 = new WLeastSquareMethod().estimate(V, U);
			bH1.add(v1);
			bWH1.add(v2);
			difH1.add(v1 - v2);

			ProbDistribution distKsi0 = new NormalDistribution(0.0, 0.0);
			modelData(distX, distKsi0, distEps, N, b0, b1);
			Double v3 = new LeastSquareMethod().estimate(V, U);
			Double v4 = new WLeastSquareMethod().estimate(V, U);
			bH0.add(v3);
			bWH0.add(v4);
			difH0.add(v3 - v4);
			Double tmp = 0.0;
			Double tmp1 = 0.0;
			Double tmp2 = 0.0;
			for (Double u : U) {
				tmp += u * u;
				tmp1 += Math.pow(u, 4.0);
				tmp2 += Math.pow(u, 6.0);
			}
			S1 += 1.0 / tmp;
			S2 += tmp2 / (tmp1 * tmp1);

		}
		System.out.printf("bH0: %f4.4 \n", getStdDev(bH0));
		System.out.printf("bH0_teor: %f4.4 \n", Math.sqrt((S1 / M) * 0.1));
		System.out.printf("bWH0: %f4.4 \n", getStdDev(bWH0));
		System.out.printf("bWH0_teor: %f4.4 \n", Math.sqrt((S2 / M) * 0.1));
		System.out.printf("bWH0-bH0: %f4.4 \n", getStdDev(bWH0)
				- getStdDev(bH0));
		System.out.printf("difH0: %f4.4 \n", getStdDev(difH0));
		System.out.printf("cov: %f4.4 \n", getCov(bH0, bWH0));
		//
		// System.out.println("bH0  "+ getStdDev(bH0));
		// System.out.println("bWH0  "+ getStdDev(bWH0));
		// System.out.println("difH0  "+ getStdDev(difH0));

		// out.flush();
		// out.close();
	}

	void testLAMEstCov() throws FileNotFoundException {
		ProbDistribution distX = new ExpDistribution(0.75);
		ProbDistribution distEps = new NormalDistribution(0.0, 0.5);

		int nSet[] = { 50, 100, 200, 300, 500,1000 };
		// int n =200;
		int M = 5000;
		Double b0 = 0.5;
		Double b1 = 1.0;
		double s2Ksi = 0.5d;
		Map<Double, Double> V_U = new TreeMap<Double, Double>();

		PrintWriter out = new PrintWriter(new FileOutputStream(
				"EstPropertties_H0_5.csv", true));
		// out.println("Cov"getCov(b11, b12));
//		out.println(",N,LAM,WLAM,LSM,WLSM,LAM est, WLAM est, DiffVarEst, Cov(LAM,LSM), EstCov(LAM,LSM)");
//		out.println("N,LAM,E{LAM est},D{LAM est},WLAM,E{WLAM est},D{WLAM est}");
		out.println("N,D[LAM-WLAM],estD[LAM-WLAM],EstErr,D[LAM-LSM],estD[LAM-LSM],EstErr");
		for (int n : nSet) {
			StructuralModel model = new StructuralModel(n, distX,
					new NormalDistribution(0.0, 0.0), distEps, b0, b1);
			List<Double> b11 = new ArrayList<Double>();
			List<Double> b12 = new ArrayList<Double>();
			List<Double> b13 = new ArrayList<Double>();
			List<Double> b14 = new ArrayList<Double>();

			List<Double> var1 = new ArrayList<Double>();
			List<Double> var2 = new ArrayList<Double>();
			List<Double> var3 = new ArrayList<Double>();

			
			for (int j = 0; j < M; j++) {
				V_U = MathCommon.Normalize(model.getData());
				V = new ArrayList<Double>(V_U.values());
				U = new ArrayList<Double>(V_U.keySet());
				Double b_LAM = new LeastAbsMethod().estimate(V, U);
				Double b_WLAM = new WLeastAbsMethod().estimate(V, U);
				Double b_LSM = new LeastSquareMethod().estimate(V, U);
				b11.add(b_LAM);
				b12.add(b_WLAM);
				b13.add(b_LSM);
				b14.add(new WLeastSquareMethod().estimate(V, U));
				List<Double> err = new ArrayList<Double>();
				Double S2 = 0.0;
				Double S4 = 0.0;
				Double S6 = 0.0;
				Double absNu = 0.0;
				for (int i = 0; i < U.size(); i++) {
					S2 += Math.pow(U.get(i), 2.0);
					S6 += Math.pow(U.get(i), 6.0);
					S4 += Math.pow(U.get(i), 4.0);
					absNu += Math.abs(V.get(i)-U.get(i)*b_LAM);
					err.add(V.get(i)-U.get(i)*b_LAM);
				}
			
				
				//Estimation of f(0)
				Double G1 = 0.0;
				Double G2 = 0.0;
				Double h = Math.pow(U.size(), 1.0/8.0);
				for (int k = 0; k < U.size(); k++) {
					Double u = U.get(k);
					G1 += u*Math.signum(V.get(k)-u*b_LAM + (u*h)/Math.sqrt(S2));
					G2 += u*Math.signum(V.get(k)-u*b_LAM - (u*h)/Math.sqrt(S2));
				}
				G1 *= 1.0/Math.sqrt(S2);
				G2 *= 1.0/Math.sqrt(S2);
				Double f0 = 2*h/(G1-G2);
				//
				var1.add(f0*f0/S2);
				var2.add(f0*f0*S6/Math.pow(S4, 2.0));
				var3.add(f0*f0/S2 + MathCommon.getVar(err)/S2 - 2*f0*absNu/(n*S2));
				

			}

			
			List<Double> diff = new ArrayList<Double>();
			List<Double> diff1 = new ArrayList<Double>();
			for (int i = 0; i < b11.size(); i++) {
				diff.add(b11.get(i) - b12.get(i));
				diff1.add(b11.get(i) - b13.get(i));
			}
			
//			out.println(n+","+MathCommon.getVar(b11)+","+MathCommon.getMean(var1)+","+ MathCommon.getVar(var1)+
//					","+MathCommon.getVar(b12)+","+MathCommon.getMean(var2)+","+MathCommon.getVar(var2));
//			
			Double diffVar = MathCommon.getVar(diff);
			Double diff1Var = MathCommon.getVar(diff1);
			Double diffVarEst = MathCommon.getMean(var2)-MathCommon.getMean(var1);
			Double diff1VarEst = MathCommon.getMean(var3);
			
			out.println(n + "," + diffVar + "," + diffVarEst + "," + Math.abs(diffVar-diffVarEst)/diffVar +
					"," + diff1Var + "," + diff1VarEst + "," + Math.abs(diff1Var-diff1VarEst)/diff1Var);
		}

		out.flush();
		out.close();
	}

	void testF0Est() throws FileNotFoundException {
		ProbDistribution distX = new ExpDistribution(0.75);
		ProbDistribution distEps = new NormalDistribution(0.0, 0.5);

		int nSet[] = { 50, 100, 200, 300, 500, 1000 };
		// int n =200;
		int M = 5000;
		Double b0 = 0.5;
		Double b1 = 1.0;

		double s2Ksi = 0.5d;
		Map<Double, Double> V_U = new TreeMap<Double, Double>();

		PrintWriter out = new PrintWriter(new FileOutputStream("F0Est.csv",
				true));
		// out.println("Cov"getCov(b11, b12));
		out.println("N,F0,F0Est");
		for (int n : nSet) {
			Double h = Math.pow(n, 1.0 / 8.0);
			StructuralModel model = new StructuralModel(n, distX,
					new NormalDistribution(0.0, 0.0), distEps, b0, b1);
			Double F = 0.0;
			for (int j = 0; j < M; j++) {
				V_U = MathCommon.Normalize(model.getData());
				V = new ArrayList<Double>(V_U.values());
				U = new ArrayList<Double>(V_U.keySet());
				Double b = new LeastAbsMethod().estimate(V, U);
				Double S2 = 0.0;
				Double G1 = 0.0;
				Double G2 = 0.0;
				for (int i = 0; i < U.size(); i++) {
					Double u = U.get(i);
					S2 += Math.pow(u, 2.0);

				}
				S2 = Math.sqrt(S2);
				for (int i = 0; i < U.size(); i++) {
					Double u = U.get(i);
					G1 += u * Math.signum(V.get(i) - u * b + (u * h) / S2);
					G2 += u * Math.signum(V.get(i) - u * b - (u * h) / S2);
				}
				G1 *= 1.0 / S2;
				G2 *= 1.0 / S2;
				F += 2 * h / (G1 - G2);
			}

			// for (int i=0; i<U.size(); i++) {
			// Double u = U.get(i);
			// S2 += Math.pow(u,2.0);
			// G1 += u*Math.signum(V.get(i)-u*b + (u*h)/Math.sqrt(n));
			// G2 += u*Math.signum(V.get(i)-u*b - (u*h)/Math.sqrt(n));
			//
			// }
			//
			// G1 *= 1.0/Math.sqrt(n);
			// G2 *= 1.0/Math.sqrt(n);
			// F += 2*S2*h/(n*(G1-G2));
			//
			// }
			F = (double) F / M;

			out.println(n + "," + Math.sqrt(Math.PI * 0.5 / 2.0) + "," + F);
		}

		out.flush();
		out.close();

	}

	public static void main(String arg[]) throws FileNotFoundException {
		EstimationProperties test = new EstimationProperties();
		// test.testDistributionWithoutErrors("CoeffsH0.csv");
		// test.testDistributionWithErrors("CoeffsH1.csv");
		test.testLAMEstCov();
	}

}
